/*
Java board game library.
Copyright (C) 2011  Deepesh Garg

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package d.g.boardgames.chess.player;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import d.g.boardgames.chess.ChessBoard;
import d.g.boardgames.chess.ChessMove;
import d.g.boardgames.chess.CoordinateMoveParser;
import d.g.boardgames.chess.FENSerializer;
import d.g.boardgames.chess.PGNMoveParser;


public class XBoardSource implements MoveSource {

	String xboardCommand = "gnuchess xboard";
	ChessBoard chessBoard;
	
	Process process;
	Thread t;
	InputStream inputStream;
	OutputStream outputStream;
	FENSerializer fenSerializer;
	PGNMoveParser pgnMoveParser;
	CoordinateMoveParser coordinateMoveParser;
	
	SourceDelegatePlayer moveSink;
	
	@Override
	public void registerSink(SourceDelegatePlayer sink) {
		moveSink = sink;
		initProcess();
        fenSerializer = new FENSerializer();
        pgnMoveParser = new PGNMoveParser();
        coordinateMoveParser = new CoordinateMoveParser();
	}

	@Override
	public void startThinking(ChessBoard cb) {
		chessBoard = cb;
		String boardPosition = fenSerializer.getString(cb);
		try {
			if (process == null) {
				initProcess();
			}
			outputStream.write (("setboard " + boardPosition + "\n").getBytes()); outputStream.flush();
			XBoardGetMove xboardGetMove = new XBoardGetMove();
			t = new Thread (xboardGetMove);
			t.setName("xboard-source-thread");
			t.start();
		} catch (IOException ioe) {
			throw new RuntimeException("Failed to set position");
		}
	}

	public void stopThinking() {
		if (t != null) {
			t.interrupt();
			t = null;
		}
		destroyProcess();
	}

	public String getXboardCommand() {
		return xboardCommand;
	}

	public void setXboardCommand(String xboardCommand) {
		this.xboardCommand = xboardCommand;
	}
	
	private void initProcess() {
		try {
			process = Runtime.getRuntime().exec (xboardCommand);
	        inputStream = process.getInputStream(); //stream reading from process standard output.
	        outputStream = process.getOutputStream(); //stream writing to process standard input.
	        outputStream.write (("easy" + "\n").getBytes()); outputStream.flush();
	        outputStream.write (("st 3" + "\n").getBytes()); outputStream.flush();
	        outputStream.write (("force" + "\n").getBytes()); outputStream.flush();
		} catch (IOException ioe) {
			throw new RuntimeException("Failed to execute " + xboardCommand);
		}
	}
	
	private void destroyProcess() {
		if (process != null) {
			process.destroy();
			process = null;
		}
	}
	
	class XBoardGetMove implements Runnable {
		public void run() {
			try {Thread.sleep(10);} catch (InterruptedException ie) {return;}
			try {
				outputStream.write (("go" + "\n").getBytes()); outputStream.flush();
				Pattern p = Pattern.compile (".*move.* (.*)$");
				boolean waitMore = true;
				String input = "";
		        while (inputStream.available() >= 0 && waitMore) {
	            	if (inputStream.available() == 0) {
	            		Thread.sleep(1000);
	            	} else {
		            	int readByte = inputStream.read();
		                input += (char)readByte;
		                if ((char)readByte == '\r' || (char)readByte == '\n') {
			                Matcher m = p.matcher(input);
			                if (m.find()) {
			                	String moveString = m.group(1);
			                    ChessMove chessMove = coordinateMoveParser.parseMoveString(chessBoard, moveString);
			                    if (chessMove == null) {
			                    	chessMove = pgnMoveParser.parseMoveString(chessBoard, moveString);
			                    	System.out.println("coordMoveParser returned null and pgnMoveParser returned " + chessMove);
			                    } else {
			                    	System.out.println("coordMoveParser returned " + chessMove);
			                    }
			                    moveSink.notifyMove(chessMove);
			                    waitMore = false;
			                }
			                input = "";
		                }
	            	}
		        }
			} catch (IOException ioe) {
				throw new RuntimeException("Failed to get move");
			} catch (InterruptedException ie) {
				destroyProcess();
				moveSink.notifyMove(null);
			}
		}
	}
	
	public void finalize() {
		if (process != null) {
			process.destroy();
		}
	}
}
